/**
 * WebCrawler.java
 * Version: 1.0
 * Created in 06/08/2013
 * Created by Gerson Groth
 * 
 * Copyright
 */
package webcrawler.activities.webcrawler;

import java.io.File;
import java.nio.file.AccessDeniedException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

import webcrawler.activities.data.Data;
import webcrawler.activities.download.DownloadActivity;
import webcrawler.activities.view.utils.Browsers;
import br.webcrawler.activities.webcrawler.utils.FileManager;
import br.webcrawler.activities.webcrawler.xml.Constants;

/**
 * The Class WebCrawler.
 *
 * @author Gerson Groth
 */
public class WebCrawler implements Runnable {

	/** The logger. */
	private Logger logger = Logger.getLogger(WebCrawler.class);

	/** The driver. */
	private WebDriver driver;
	
	/** The data. */
	private Data data;
	
	/** The wait. */
	private WebDriverWait wait;
	
	/** The corridas. */
	private ArrayList<String> corridas;
	
	/** The start. */
	private long start;
	
	/** The end. */
	private long end;
	
	/** The sequencial download. */
	private boolean sequencialDownload = false;
	
	/** The web search. */
	private boolean webSearch = false;

	/**
	 * Instantiates a new web crawler.
	 *
	 * @param data the data
	 */
	public WebCrawler(Data data) {
		this.data = data;
	}

	/**
	 * Login.
	 */
	private void login() {

		LoginWebCrawler login = new LoginWebCrawler(data);
		login.doLogin(driver);

	}

	/**
	 * Explore.
	 */
	private void explore() {
		logger.info("Opening explore page");

		driver.get("http://connect.garmin.com/explore");

		waiting();
	}

	/**
	 * Waiting.
	 */
	private void waiting() {
		WebElement myDynamicElement;

		try {
			myDynamicElement = wait.until(new ExpectedCondition<WebElement>() {
				public WebElement apply(WebDriver d) {
					WebElement element = d.findElement(By
							.id("_viewRoot:status.start"));
					if (element == null)
						return null;
					else {
						if (element.isDisplayed())
							return element;
						else
							return null;
					}
				}
			});
		} catch (Exception e) {
		}
		try {
			myDynamicElement = wait.until(new ExpectedCondition<WebElement>() {
				public WebElement apply(WebDriver d) {
					WebElement element = d.findElement(By
							.id("_viewRoot:status.start"));
					if (element == null)
						return null;
					else {
						if (!element.isDisplayed())
							return element;
						else
							return null;
					}
				}
			});
		} catch (Exception e) {
		}
	}

	/**
	 * Inits the search.
	 */
	private void initSearch() {
		WebElement keyword = driver.findElement(By
				.id("exploreSearchForm:keyword"));
		WebElement location = driver.findElement(By
				.id("exploreSearchForm:location"));
		WebElement activityType = driver.findElement(By
				.id("exploreSearchForm:activityType"));
		WebElement eventType = driver.findElement(By
				.id("exploreSearchForm:eventType"));
		WebElement timePeriod = driver.findElement(By
				.id("exploreSearchForm:timePeriodSelect"));
		WebElement startDate = driver.findElement(By
				.id("exploreSearchForm:startDateCalendarInputDate"));
		WebElement endDate = driver.findElement(By
				.id("exploreSearchForm:endDateCalendarInputDate"));
		WebElement startDistance = driver.findElement(By
				.id("exploreSearchForm:activitySummarySumDistance-min"));
		WebElement endDistance = driver.findElement(By
				.id("exploreSearchForm:activitySummarySumDistance-max"));
		WebElement startTime = driver.findElement(By
				.id("exploreSearchForm:activitySummarySumDuration-min"));
		WebElement endTime = driver.findElement(By
				.id("exploreSearchForm:activitySummarySumDuration-max"));
		WebElement startElevation = driver.findElement(By
				.id("exploreSearchForm:activitySummaryGainElevation-min"));
		WebElement endElevation = driver.findElement(By
				.id("exploreSearchForm:activitySummaryGainElevation-max"));
		WebElement displayName = driver.findElement(By
				.id("exploreSearchForm:username"));
		WebElement myActivitiesOnly = driver.findElement(By
				.id("exploreSearchForm:myActivities"));

		WebElement showFilters = driver.findElement(By.id("showFilters"))
				.findElements(By.linkText("Show Filters")).get(0);
		WebElement search = driver.findElement(By.id("searchButton"));

		showFilters.click();

		if (!"".equals(data.getKeyword())) {
			keyword.sendKeys(data.getKeyword());
		}

		if (!"".equals(data.getLocation())) {
			location.clear();
			location.sendKeys(data.getLocation());
		}

		Select select = new Select(timePeriod);
		select.selectByValue(data.getTimePeriod());
		select = new Select(activityType);
		select.selectByValue(data.getActivityType());
		select = new Select(eventType);
		select.selectByValue(data.getEventType());

		if (Constants.CUSTOM_DATE.equals(data.getTimePeriod())) {
			JavascriptExecutor js = (JavascriptExecutor) driver;
			js.executeScript(
					"document.getElementById('exploreSearchForm:startDateCalendarInputDate').removeAttribute('readonly');",
					"");
			js.executeScript(
					"document.getElementById('exploreSearchForm:endDateCalendarInputDate').removeAttribute('readonly');",
					"");
			startDate.sendKeys(data.getDateInit());
			endDate.sendKeys(data.getDateEnd());
		}

		startDistance.sendKeys(data.getDistanceInit());
		endDistance.sendKeys(data.getDistanceEnd());

		startTime.sendKeys(data.getTimeInit());
		endTime.sendKeys(data.getTimeEnd());

		startElevation.sendKeys(data.getElevationInit());
		endElevation.sendKeys(data.getElevationEnd());

		displayName.sendKeys(data.getDisplayName());

		if (data.isMyActivitiesOnly()) {
			myActivitiesOnly.click();
		}

		search.click();

		// showFilters.click();
	}

	/**
	 * Gets the running.
	 *
	 * @return the running
	 */
	private void getRunning() {
		int pagina = 1;

		WebElement next = null;

		do {
			logger.info("");
			logger.info("Iniciando página " + pagina);

			waiting();

			List<WebElement> elements = driver.findElements(By
					.className("rich-datascr-button"));
			for (WebElement e : elements) {
				if (e.getText().equals("»")) {
					next = e;
					break;
				}
			}
			logger.info("Listando atividades página " + pagina);

			List<WebElement> tableRows = driver.findElements(By
					.className("rich-table-row"));

			for (WebElement e : tableRows) {
				WebElement resultsEntry = e.findElement(By
						.className("resultsEntry"));
				WebElement resultsTitle = resultsEntry.findElement(By
						.className("resultsTitle"));
				WebElement a = resultsTitle.findElement(By.tagName("a"));
				String[] activity = a.getAttribute("href").split("/");

				logger.info("Activity " + activity[activity.length - 1] + " - "
						+ a.getText());

				corridas.add(a.getAttribute("href"));
			}

			pagina++;
			if (next != null) {
				next.click();
			}
		} while (next != null
				&& !(next.getAttribute("class")
						.contains("rich-datascr-button-dsbld")));

		logger.info("Não há mais atividades");
		logger.info("Total de atividades " + corridas.size());
		logger.info("");

	}

	/**
	 * Download activities.
	 */
	private void downloadActivities() {
		logger.info("Iniciando o download das atividades");
		DownloadActivity downloadActivity = new DownloadActivity(data);
		long activity = 0;
		for (String s : corridas) {
			logger.info("Fazendo o download da activity : " + s);

			try {
				driver.get(s);
				try {
					WebElement sigIn = driver.findElement(By
							.className("nav-logged-in"));
					if (sigIn == null) {
						login();
						driver.get(s);
					}
				} catch (NoSuchElementException ne) {
					login();
					driver.get(s);
				}
				String[] ss = s.split("/");
				activity = Long.parseLong(ss[ss.length - 1]);

				downloadActivity.verifyUser(driver.getPageSource());

				if (data.isDownloadXml())
					downloadActivity.download(driver.getPageSource(), activity);
				downloadActivity.setCookies(driver.manage().getCookies());
				if (data.isDownloadTcx())
					downloadActivity.downloadTcx(activity);
				if (data.isDownloadGpx())
					downloadActivity.downloadGpx(activity);
			} catch (AccessDeniedException e) {
				logger.warn(e.getMessage());
			} catch (Exception e) {
				logger.warn("Error downloading activity: " + activity + " ->"
						+ e.getMessage());
			}
		}
		try {
			if (data.isDownloadXml())
				downloadActivity.downloadAll();
		} catch (Exception e) {
			logger.error("Error saving xml file 'activitiesAll.xml'");
		}
	}

	/**
	 * Validate.
	 */
	private void validate() {
		logger.info("Verificando se todos os arquivos foram baixados");
		ArrayList<String> ler = FileManager.ler(Data.getInstance()
				.getXmlFolder() + "activities.xml");
		ArrayList<String> atividadesTcx = new ArrayList<String>();
		ArrayList<String> atividadesXml = new ArrayList<String>();
		ArrayList<String> atividadesGpx = new ArrayList<String>();
		for (String s : ler) {
			if (s.contains("file=")) {
				String activity = s.split("\"")[1];
				activity = activity.substring(0, activity.lastIndexOf('.'));
				atividadesTcx.add(activity);
				atividadesXml.add(activity);
				atividadesGpx.add(activity);
			}
		}

		DownloadActivity downloadActivity = new DownloadActivity(data);

		if (data.isDownloadGpx()) {

			// conferir .gpx
			File gpxFolder = new File(Data.getInstance().getGpxFolder());
			File[] listGpx = gpxFolder.listFiles();

			if (listGpx != null) {
				for (File f : listGpx) {
					try {
						String extension = f.getName().substring(
								f.getName().lastIndexOf('.'));
						if (".gpx".equals(extension))
							atividadesGpx.remove(f.getName().substring(0,
									f.getName().lastIndexOf('.')));
					} catch (Exception e) {
						logger.error("Error: ", e);
					}
				}
				for (String s : atividadesGpx) {
					int activity = Integer.parseInt(s.split("_")[1]);
					logger.info("Fazendo o download da activity : " + s);
					String d = "http://connect.garmin.com/activity/"
							+ s.split("_")[1];
					logger.info("Baixando novamente activity (gpx): " + d);
					try {
						driver.get(d);
						try {
							WebElement sigIn = driver.findElement(By
									.className("nav-logged-in"));
							if (sigIn == null) {
								login();
								driver.get(d);
							}
						} catch (NoSuchElementException ne) {
							login();
							driver.get(d);
						}
						downloadActivity.setCookies(driver.manage()
								.getCookies());
						downloadActivity.downloadGpx(activity);
					} catch (Exception e) {
						logger.error("Erro ao fazer download: (" + d + ") ->"
								+ e.getMessage());
					}
				}
			}
		}

		if (data.isDownloadTcx()) {
			// conferir .tcx
			File tcxFolder = new File(Data.getInstance().getTcxFolder());
			File[] listTcx = tcxFolder.listFiles();

			if (listTcx != null) {
				for (File f : listTcx) {
					try {
						String extension = f.getName().substring(
								f.getName().lastIndexOf('.'));
						if (".tcx".equals(extension))
							atividadesTcx.remove(f.getName().substring(0,
									f.getName().lastIndexOf('.')));
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				for (String s : atividadesTcx) {
					int activity = Integer.parseInt(s.split("_")[1]);
					logger.info("Fazendo o download da activity : " + s);
					String d = "http://connect.garmin.com/activity/"
							+ s.split("_")[1];
					logger.info("Baixando novamente activity (tcx): " + d);
					try {
						driver.get(d);
						try {
							WebElement sigIn = driver.findElement(By
									.className("nav-logged-in"));
							if (sigIn == null) {
								login();
								driver.get(d);
							}
						} catch (NoSuchElementException ne) {
							login();
							driver.get(d);
						}
						downloadActivity.setCookies(driver.manage()
								.getCookies());
						downloadActivity.downloadTcx(activity);
					} catch (Exception e) {
						logger.error("Erro ao fazer download: (" + d + ") ->"
								+ e.getMessage());
					}
				}
			}
		}

		if (data.isDownloadXml()) {

			// conferir .xml
			File xmlFolder = new File(Data.getInstance().getXmlFolder());
			File[] listXml = xmlFolder.listFiles();

			if (listXml != null) {
				for (File f : listXml) {
					try {
						String extension = f.getName().substring(
								f.getName().lastIndexOf('.'));
						if (".xml".equals(extension))
							atividadesXml.remove(f.getName().substring(0,
									f.getName().lastIndexOf('.')));
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				for (String s : atividadesXml) {
					int activity = Integer.parseInt(s.split("_")[1]);
					logger.info("Fazendo o download da activity : " + s);
					String d = "http://connect.garmin.com/activity/"
							+ s.split("_")[1];
					logger.info("Baixando novamente activity (xml): " + d);
					try {
						driver.get(d);
						try {
							WebElement sigIn = driver.findElement(By
									.className("nav-logged-in"));
							if (sigIn == null) {
								login();
								driver.get(d);
							}
						} catch (NoSuchElementException ne) {
							login();
							driver.get(d);
						}
						downloadActivity.download(driver.getPageSource(),
								activity);
					} catch (Exception e) {
						logger.error("Erro ao fazer download: (" + d + ") ->"
								+ e.getMessage());
					}
				}
			}
		}
	}

	/**
	 * Finish.
	 */
	private void finish() {
		logger.info("Closing browser");
		try {
			quit();
		} catch (Exception e) {
			logger.warn("Error closing browser", e);
		}
	}

	// @AfterClass
	/**
	 * Quit.
	 */
	private void quit() {
		driver.quit();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		driver = data.getBrowser().getDriver();
		wait = new WebDriverWait(driver, 20);
		corridas = new ArrayList<String>();
		try {
			login();
			if (this.sequencialDownload) {
				downloadSequencialActivities();
			} else {
				explore();
				if (this.webSearch)
					waitSearch();
				else
					initSearch();
				getRunning();
				downloadActivities();
				validate();
			}
		} catch (Exception e) {
			logger.error("Error: ", e);
		}
		finish();

	}

	/**
	 * Wait search.
	 */
	private void waitSearch() {
		WebElement myDynamicElement = null;
		while (myDynamicElement == null) {
			try {
				myDynamicElement = wait
						.until(new ExpectedCondition<WebElement>() {
							public WebElement apply(WebDriver d) {
								WebElement element = d.findElement(By
										.id("_viewRoot:status.start"));
								if (element == null)
									return null;
								else {
									if (element.isDisplayed())
										return element;
									else
										return null;
								}
							}
						});
			} catch (Exception e) {
			}
		}
		try {
			myDynamicElement = wait.until(new ExpectedCondition<WebElement>() {
				public WebElement apply(WebDriver d) {
					WebElement element = d.findElement(By
							.id("_viewRoot:status.start"));
					if (element == null)
						return null;
					else {
						if (!element.isDisplayed())
							return element;
						else
							return null;
					}
				}
			});
		} catch (Exception e) {
		}

	}

	/**
	 * Download sequencial activities.
	 */
	private void downloadSequencialActivities() {
		logger.info("Start downloading activities");
		DownloadActivity downloadActivity = new DownloadActivity(data);
		for (long i = this.start; i <= this.end; i++) {
			logger.info("Downloading activity : " + i);
			String s = "http://connect.garmin.com/activity/" + i;
			try {
				driver.get(s);
				try {
					WebElement sigIn = driver.findElement(By
							.className("nav-logged-in"));
					if (sigIn == null) {
						login();
						driver.get(s);
					}
				} catch (NoSuchElementException ne) {
					login();
					driver.get(s);
				}

				downloadActivity.verifyUser(driver.getPageSource());

				if (data.isDownloadXml())
					downloadActivity.download(driver.getPageSource(), i);
				downloadActivity.setCookies(driver.manage().getCookies());
				if (data.isDownloadTcx())
					downloadActivity.downloadTcx(i);
				if (data.isDownloadGpx())
					downloadActivity.downloadGpx(i);
			} catch (AccessDeniedException e) {
				logger.warn(e.getMessage());
			} catch (Exception e) {
				logger.warn("Error downloading activity: " + i + " ->"
						+ e.getMessage());
			}
		}
		try {
			if (data.isDownloadXml())
				downloadActivity.downloadAll();
		} catch (Exception e) {
			logger.error("Error saving xml file 'activitiesAll.xml'");
		}
	}

	/**
	 * Sets the sequencial download.
	 *
	 * @param startActivity the start activity
	 * @param endActivity the end activity
	 */
	public void setSequencialDownload(String startActivity, String endActivity) {
		this.sequencialDownload = true;

		try {
			this.start = Long.parseLong(startActivity);
		} catch (Exception exc) {
			this.start = 1;
		}
		try {
			this.end = Long.parseLong(endActivity);
		} catch (Exception exc) {
			this.end = Long.MAX_VALUE;
		}

	}

	/**
	 * Checks if is web search.
	 *
	 * @return true, if is web search
	 */
	public boolean isWebSearch() {
		return webSearch;
	}

	/**
	 * Sets the web search.
	 *
	 * @param webSearch the new web search
	 */
	public void setWebSearch(boolean webSearch) {
		this.webSearch = webSearch;
	}

}
